/*
 * Copyright (c) 2020, yifengling0. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. No personal names or organizations' names associated with the
 *    Atomthreads project may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include "atom.h"
#include <stdio.h>
#include <string.h>
#include "console.h"
#include "debug.h"
#include "iosdrv.h"
#include "sio.h"
#include "shell.h"

#ifdef COM_USE_CONSOLE

#define PROMPT	"[maple~]#"

SLIST_HEAD(Pool, Node);
static struct Pool _header;

static char* _parameters[COM_CONSOLE_MAX_PARAM_COUNT] = { NULL };

typedef struct _CommandHistory{
    int index;
    int show;
    char history[COM_CONSOLE_HISTORY][COM_CONSOLE_CMD_MAXLEN];
}CommandHistory;

static enConsoleResult console_input(char *command);

static CommandHistory _history = {0};

static enConsoleResult help(int argc, char** argv)
{
	Node* item;
	int count = 0;

	KPrint("\r\nCommand list:\r\n");

	SLIST_FOREACH(item, &_header, node) {
		COMMAND* cmd = (COMMAND*)item;
        int len = strlen(cmd->name);
		if( len > 0 ){
#ifdef COM_CONSOLE_HELPINFO
            KPrint("  ");KPrint(cmd->name);
            for(int i=0; i < COM_CONSOLE_PREFIX_SPACE - len; i++){
                KPrint(" ");
            }
            KPrint("----  ");KPrint(cmd->help);
            KPrint("\r\n");
			count++;
#else
			KPrint("\t");
			KPrint(cmd->name);
			count++;
			if (count % 8 == 0) {
				KPrint("\r\n");
			}
#endif            
		}
	}

	return CE_OK;
}

BGEIN_DEF_CMD(_helpCommand)
    CMD_DEF(help, "show command list")
END_DEF_CMD()

/* Application threads' TCBs */
static ATOM_TCB console_tcb = DEFAULT_TCB("console");

/* Main thread's stack area */
static uint8_t console_thread_stack[COM_CONSOLE_STACK_SIZE_BYTES];

typedef struct _tCommand {
	char buffer[COM_CONSOLE_CMD_MAXLEN];
	int index;
    int count;
}tCommand;

static tCommand _debug_command;

static void print_result(enConsoleResult result)
{
    switch(result) {
    case CE_OK:
        break;
    case CE_NOT_FOUND:
        KPrint("Bad command!\r\n");
        break;
    case CE_PARAM_ERROR:
        KPrint("Parameter error!\r\n");
        break;
    case CE_RUNTIME_ERROR:
        KPrint("Runtime error!\r\n");
        break;
    case CE_ACCESS_FORBIDDEN:
        KPrint("Access forbidden!\r\n");
        break;
    default:
        KPrintValue("code:", result, 16, 0, 1);
        break;
    }
    
	KPrint("\r\n");
}

static void show_match_cmd_list(const char *str)
{
    Node* item;
    int count = 0;
    const char *first = NULL;

    SLIST_FOREACH(item, &_header, node) {
   		COMMAND* cmd = (COMMAND*)item;
        if( strstr(cmd->name, str) == cmd->name ){
            count++;
            first = cmd->name;
        }
	}

    if( count > 1 ){
        count = 0;
        KPrint("\r\n");
        SLIST_FOREACH(item, &_header, node) {
   		    COMMAND* cmd = (COMMAND*)item;
            if( strstr(cmd->name, str) == cmd->name ){
                KPrint("\t");
                KPrint(cmd->name);
                count++;
                if (count % 5 == 0) {
                    KPrint("\r\n");
                }
            }
	    }
        KPrint("\r\n");
        KPrint(PROMPT);
        KPrint(str);
    }else{
      if(first != NULL){
        int len = strlen(first);
        memcpy(_debug_command.buffer, first, len);
        _debug_command.index = len;
        KPrint("\r");
        KPrint(PROMPT);
        KPrint(first);
      }
    }
}


static void show_history_cmd(int arrow)
{
    _history.show = _history.show + arrow;

    if( _history.show < 0 ){
        _history.show = COM_CONSOLE_HISTORY - 1;
    }

    if( _history.show >= COM_CONSOLE_HISTORY ){
        _history.show = 0;
    }

    if( strlen(&_history.history[_history.show][0]) > 0 ){
        //应对控制命令
        KPrint(" \r");
        KPrint(PROMPT);
        //清除之前的命令
        int len_curr = strlen(_debug_command.buffer);
        for( int i = 0; i <= len_curr; i++){
          KPrint(" ");
        }

        KPrint("\r");
        KPrint(PROMPT);
        int len =  strlen(&_history.history[_history.show][0]);
        memcpy(_debug_command.buffer, &_history.history[_history.show], len);
        _debug_command.buffer[len] = 0;
        _debug_command.index = len;
        KPrint(&_history.history[_history.show][0]);
    }
}

static void save_history_cmd(const char *str)
{
    int len = strlen(str);

    if( len > 0 ){
        memcpy(_history.history[_history.index], str, len);
        _history.history[_history.index][len] = '\0';
        _history.index++;
        if( _history.index >= COM_CONSOLE_HISTORY ){
            _history.index = 0;
        }
        _history.show = _history.index;
    }
}

static void console_thread (uint32_t data)
{
	enConsoleResult result;
    int arrow = 0; // 1: 1b 2: 1b 5b
	_debug_command.index = 0;
    _debug_command.count = 0;
	memset(_debug_command.buffer, 0, COM_CONSOLE_CMD_MAXLEN);
	KPrint(PROMPT);

	while (1) {
		if (DebugPortIsReadable()) {
			char c = DebugPortGetChar();
			if (c == 0x0d) {
				KPrint("\r\n");
                if( _debug_command.index > 0){
                    save_history_cmd(_debug_command.buffer);
                    result = console_input(_debug_command.buffer);
                    print_result(result);
                    memset(_debug_command.buffer, 0, COM_CONSOLE_CMD_MAXLEN);
                    _debug_command.index = 0;
                    _debug_command.count = 0;
                }
				KPrint(PROMPT);
			}else if( c == '\b'){
				if( _debug_command.index > 0 ){
					_debug_command.index--;
					_debug_command.count--;
                    _debug_command.buffer[_debug_command.index] = 0;
                    KPrint("\b \b");
				}
            }else if( arrow == 2 && c == 0x43 ){  /* right key */
                if (_debug_command.index < _debug_command.count)
                {
                    _debug_command.index++;
                    DebugPortPutChar(c);
                }
            }else if( arrow == 2 && c == 0x44 ){ /* left key */
                if (_debug_command.index > 0)
                {
                    _debug_command.index--;
                    DebugPortPutChar(c);
                }
            }else if( arrow == 2 && c == 0x41 ){ /* up key */
                show_history_cmd(-1);
            }else if( arrow == 2 && c == 0x42 ){ /* down key */
                show_history_cmd(1);
            }else if( c == '\t' ){ /* down key */
                if (_debug_command.index == 0){
                    help(0, NULL);
                    KPrint("\r\n");
                    KPrint(PROMPT);
                }else{
                    show_match_cmd_list(_debug_command.buffer);
                }
            }else {
                DebugPortPutChar(c);
                if( c == 0x1b ){    //处理箭头控制字符
                    arrow = 1;
                    continue;
                }else if( arrow == 1 && c == 0x5b){
                    arrow = 2;
                    continue;
                }
                arrow = 0;
				_debug_command.buffer[_debug_command.index] = c;
				_debug_command.index++;
                if( _debug_command.index > _debug_command.count){
                    _debug_command.count++;
                }
			}
		}
		else {
			atomTimerDelay(5);
		}
	}
}

void ConsoleInit()
{
	SLIST_INIT(&_header);
	CONSOLE_REG_CMD(_helpCommand);
    DebugCmdInit();
    ShellInit();
}

void ConsoleRun()
{
	/* Create an application thread */
	uint8_t status = atomThreadCreate(&console_tcb,
		COM_PROIORITY_CONSOLE, console_thread, 0,
		&console_thread_stack[0],
		COM_CONSOLE_STACK_SIZE_BYTES,
		TRUE);

	if (status != ATOM_OK) {
		KPrint("console create thread error!\r\n");
	}
}

void ConsoleRegCmd(COMMAND* cmd)
{
	SLIST_INSERT_HEAD(&_header, (Node*)cmd, node);
}

static enConsoleResult RunCommand(int argc, char** argv)
{
	enConsoleResult ret = CE_NOT_FOUND;
	Node* item;

	SLIST_FOREACH(item, &_header, node) {
		COMMAND* cmd = (COMMAND*)item;
		if (strcmp(cmd->name, argv[0]) == 0) {
			ret = cmd->action(argc, argv);
			break;
		}
	}

	return ret;
}


static enConsoleResult console_input(char *command)
{
	enConsoleResult ret;
	int count = 0;
	char* current = command;
	int remind = 0;

	for(char *p = command; *p != '\0'; p++){
		if (' ' == *p) {
			_parameters[count] = current;
			*p = '\0';
			current = p + 1;
			count++;
			remind = 0;
		}
		else {
			remind = 1;
		}
	}

	if (count == 0 || remind > 0) {
		_parameters[count] = current;
		count++;
	}
	
	ret = RunCommand(count, _parameters);

	return ret;
}

#endif //COM_USE_CONSOLE